/*
 * eopkg.c - Solus Operating System specific
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#define _GNU_SOURCE
#include <glib.h>
#include <libgen.h>
#include <libxml/xmlreader.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>

#include "eopkg.h"
#include "plugin.h"
#include "util.h"

#define PATCH_PREFIX "files/security/"

struct source_package_t *eopkg_inspect_pspec(const char *filename)
{
        xmlDocPtr doc = NULL;
        xmlNodePtr root = NULL;
        xmlNodePtr nxt = NULL;
        xmlNodePtr child = NULL;
        xmlChar *tmp = NULL;
        struct source_package_t *t = NULL;

        xmlChar *source_name = NULL;
        int release = -1;
        xmlChar *version = NULL;
        char *fpath = NULL;

        doc = xmlReadFile(filename, NULL, 0);
        if (!doc) {
                return NULL;
        }

        root = xmlDocGetRootElement(doc);
        if (!root) {
                goto clean;
        }

        if (!xmlStrEqual(root->name, BAD_CAST "PISI")) {
                fprintf(stderr, "Invalid root node\n");
                goto clean;
        }

        for (nxt = root->children; nxt; nxt = nxt->next) {
                if (nxt->type != XML_ELEMENT_NODE) {
                        continue;
                }
                if (xmlStrEqual(nxt->name, BAD_CAST "Source")) {
                        /* Grab child with "Source" name */
                        for (child = nxt->children; child; child = child->next) {
                                if (!(child->type == XML_ELEMENT_NODE && xmlStrEqual(child->name, BAD_CAST "Name"))) {
                                        continue;
                                }
                                if (!child->children) {
                                        /* bail */
                                        break;
                                }
                                if (child->children->type != XML_TEXT_NODE) {
                                        /* bail */
                                        break;
                                }
                                source_name = child->children->content;
                                break;
                        }
                } else if (xmlStrEqual(nxt->name, BAD_CAST "History")) {
                        for (child = nxt->children; child; child = child->next) {
                                if (!(child->type == XML_ELEMENT_NODE && xmlStrEqual(child->name, BAD_CAST "Update"))) {
                                        continue;
                                }
                                tmp = xmlGetProp(child, BAD_CAST "release");
                                if (!tmp) {
                                        g_warning("Missing release property");
                                        continue;
                                }
                                int t_release = atoi((const char *)tmp);
                                if (t_release > release) {
                                        release = t_release;
                                        if (version) {
                                                version = NULL;
                                        }
                                        for (xmlNodePtr sub = child->children; sub; sub = sub->next) {
                                                if (!(sub->type == XML_ELEMENT_NODE &&
                                                      xmlStrEqual(sub->name, BAD_CAST "Version"))) {
                                                        continue;
                                                }
                                                if (sub->children && sub->children->type == XML_TEXT_NODE) {
                                                        version = sub->children->content;
                                                }
                                                break;
                                        }
                                }
                                xmlFree(tmp);
                        }
                }
        }

        if (!version || !source_name) {
                goto clean;
        }

        fpath = cve_get_file_parent(filename);
        if (!fpath) {
                goto clean;
        }

        t = calloc(1, sizeof(struct source_package_t));
        if (!t) {
                free(fpath);
                goto clean;
        }
        t->name = xmlStrdup(source_name);
        t->version = xmlStrdup(version);
        t->release = release;
        t->path = fpath;
        t->xml = true; /* Ensure xmlFree is used */
        t->type = PACKAGE_TYPE_EOPKG;

clean:
        xmlFreeDoc(doc);
        return t;
}

bool eopkg_is_patched(struct source_package_t *pkg, char *id)
{
        /* Determine if its patched. */
        autofree(gchar) *pnamet = g_ascii_strdown((gchar *)id, -1);
        autofree(gchar) *pnamel = g_strdup_printf("%s/%s/%s.patch", pkg->path, PATCH_PREFIX, pnamet);
        autofree(gchar) *pname = g_strdup_printf("%s/%s/%s.patch", pkg->path, PATCH_PREFIX, id);

        if (g_file_test(pname, G_FILE_TEST_EXISTS) || g_file_test(pnamel, G_FILE_TEST_EXISTS)) {
                return true;
        }

        return false;
}

bool eopkg_is_ignored(struct source_package_t *pkg, char *id)
{
        /* Determine if its patched. */
        autofree(gchar) *pnamet = g_ascii_strdown((gchar *)id, -1);
        autofree(gchar) *pnamel = g_strdup_printf("%s/%s/%s.nopatch", pkg->path, PATCH_PREFIX, pnamet);
        autofree(gchar) *pname = g_strdup_printf("%s/%s/%s.nopatch", pkg->path, PATCH_PREFIX, id);

        if (g_file_test(pname, G_FILE_TEST_EXISTS) || g_file_test(pnamel, G_FILE_TEST_EXISTS)) {
                return true;
        }

        return false;
}

bool eopkg_is_package(const char *filename)
{
        return g_str_has_suffix((const gchar *)filename, "pspec.xml") ||
               g_str_has_suffix((const gchar *)filename, "pspec_x86_64.xml");
}

void eopkg_locate_sources(const char *directory, bool recurse, cve_add_callback cb)
{
        find_sources(directory, &eopkg_is_package, recurse, cb);
}

_module_export_ bool cve_plugin_module_init(CvePlugin *self)
{
        self->flags = PLUGIN_TYPE_PACKAGE;
        self->name = "eopkg";
        self->is_ignored = eopkg_is_ignored;
        self->is_patched = eopkg_is_patched;
        self->is_package = eopkg_is_package;
        self->scan_package = eopkg_inspect_pspec;
        return true;
}
/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
